package org.fhnw.aigs.client.GUI; import javafx.scene.Node; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; import javafx.scene.layout.RowConstraints; import org.fhnw.aigs.client.gameHandling.ClientGame; import org.fhnw.aigs.commons.communication.Message; /** * The <b>abstract</b> BaseBoard class offers a convenient base class for * classic board games like chess. It is limited to two dimensions: X and Y. * <br> * The fields of the board are defined as <b>Panes</b> which act as a very * flexible container for all kinds of fields. Due to the nature of the parent * class, <b>GridPane</b> it is possible to add new content inside the grid by * using the "add" method in the following way: <br> * board.add(field, xPosition, yPosition).<br> * Please note: It is not possible to use BaseBoard directly as it is an * abstract class.<br> * Please note: The BaseBoard is designed with square-shaped boards in mind. * While it is possible to use other ratios, it will result in distortet * graphics.<br> * v1.0 Initial release<br> * v1.1 Changes of layer handling * @version v1.1 (Raphael Stoeckli, 23.04.2015) * @author Matthias Stöckli (v1.0) */ public abstract class BaseBoard extends GridPane { /** * The fields, represented as a two dimensional array */ protected final Pane[][] fieldPanes; /** * The number of fields on the x-axis */ protected final int fieldsX; /** * The number of fields on the y-axis */ protected final int fieldsY; /** * The client game. The client game is used as "glue" between GUI and game. */ protected ClientGame clientGame; /** * The constructor of the <b>BaseBoard</b>. * * @param fieldsX The number of fields on the x-axis * @param fieldsY The number of fields on the y-axis * @param clientGame The client game. The client game is used as "glue" * between GUI and game. */ public BaseBoard(int fieldsX, int fieldsY, ClientGame clientGame) { this.setId(LayerType.board.toString()); // Sets the ID as "BOARD" this.fieldsX = fieldsX; this.fieldsY = fieldsY; this.fieldPanes = new Pane[fieldsX][fieldsY]; this.clientGame = clientGame; this.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE); // For layout purposes setConstraints(); // Defines the grid structure of the BaseBoard } /** * {@link BaseBoard#fieldsX} */ public int getFieldsX() { return fieldsX; } /** * {@link BaseBoard#fieldsY} */ public int getFieldsY() { return fieldsY; } /** * {@link BaseBoard#fieldPanes} */ public Pane[][] getFieldPanes() { return fieldPanes; } /** * Defines the grid structure of the BaseBoard. Basically, it calculates how * many percent of the available each field uses up. If the number of X and * Y fields is not equal, the bigger number will be the base of the * calculation. This prevents the fields from being distorted. Additionally * this method distributes space in the case of nonwhole numbers. E.g. if * the grid is 13 x 13 then 100/13 is not an integer. Therefore the the * percentage which every field uses must be distributed accross all fields. */ private void setConstraints() { double percentagePerField = 0; int percentagePerFieldRounded = 0; boolean xIsBigger = (double) fieldsX > (double) fieldsY; if (xIsBigger) { percentagePerField = 100 / (double) fieldsY; } else { percentagePerField = 100 / (double) fieldsX; } // Check if the percentage is a whole number/integer // This must be distributed accross the fields int distanceToInteger = 0; boolean isInteger = (percentagePerField == Math.floor(percentagePerField)) && !Double.isInfinite(percentagePerField); if (isInteger == false) { if (xIsBigger) { distanceToInteger = (int) (100 - Math.round(percentagePerField) * fieldsY); } else { distanceToInteger = (int) (100 - Math.round(percentagePerField) * fieldsX); } } percentagePerFieldRounded = (int) Math.round(percentagePerField); int distributionToRow = distanceToInteger; int distributionToColumn = distanceToInteger; // Distribute the rows. for (int i = 0; i < fieldsX; i++) { RowConstraints rc = new RowConstraints(); if (distributionToRow > 0) { rc.setPercentHeight(percentagePerFieldRounded + 1); } else if (distributionToRow < 0) { rc.setPercentHeight(percentagePerFieldRounded - 1); } else { rc.setPercentHeight(percentagePerFieldRounded); } this.getRowConstraints().add(rc); if (distributionToRow < 0) { distributionToRow++; } else if (distributionToRow > 0) { distributionToRow--; } } // Distribute the collumns for (int i = 0; i < fieldsY; i++) { ColumnConstraints cc = new ColumnConstraints(); if (distributionToColumn > 0) { cc.setPercentWidth(percentagePerFieldRounded + 1); } else if (distributionToColumn < 0) { cc.setPercentWidth(percentagePerFieldRounded - 1); } else { cc.setPercentWidth(percentagePerFieldRounded); } this.getColumnConstraints().add(cc); if (distributionToColumn < 0) { distributionToColumn++; } else if (distributionToColumn > 0) { distributionToColumn--; } } } /** * Used to define the fields of the board. Usually the appearance of the * fields will also be defined by this method. A structure like this should * work well:<br> * <br><code> * // Iterate through all fields on the x and y axis, add the field to the * for(int i = 0; i < fieldsX; i++){ * for(int j = 0; j < fieldsY; j++){ * // Instantiate a new field, add it to the fieldPanes and add the field * // as a graphical node. * YourField yourField = new TicTacToeField(); * yourBoard[i][j] = yourBoard; * this.add(yourField, i, j); * } *}</code> Where YourField is a custom field class, usually a Pane or * similar and yourBoard is the board instance. */ public abstract void setFields(); /** * In this method the event handlers will be set, i.e. what each part of the * GUI actually does. */ public abstract void setEventHandlers(); /** * This message should contain all GUI related tasks based on the messages * received. Usually this method should be called by the * {@link org.fhnw.aigs.client.gameHandling.ClientGame#processGameLogic(org.fhnw.aigs.commons.communication.Message)} method. * * @param message The message to be processed. If there is no message, just * pass null. */ public abstract void manipulateGUI(Message message); /** * Gets one of the board's fields. * * @param x The x position of the field. * @param y The y position of the field. * @return The board at the position x/y */ public Pane getField(int x, int y) { return this.fieldPanes[x][y]; } /** * * @param node The content which will replace the existing content of a * field. * @param x The x position of the field * @param y The y position of the field */ public void replaceField(Node node, int x, int y) { this.fieldPanes[x][y].getChildren().clear(); this.fieldPanes[x][y].getChildren().add(node); } }